#include "snapmanager.h"
#include "snap/automaticsnapstrategy.h"
#include "snap/nosnapstrategy.h"
#include "snap/snaptogridstrategy.h"
#include "snap/snaptoitemendpointstrategy.h"
#include "snap/snaptoitemmidpointstrategy.h"
#include "snap/snaptoitemcenterstrategy.h"
#include "snap/snaptoitemshapestrategy.h"

#include "view/scene.h"
#include "view/view.h"
#include "item/item.h"

#include "OldGraphicsView/Settings.h"

#include <QGraphicsScene>
#include <QGraphicsDropShadowEffect>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsSimpleTextItem>

#include <QActionGroup>
#include <QDebug>

namespace SymbolEditor
{

    SnapManager::SnapManager(QObject *parent):
        QObject(parent),
        m_indicatorItem(new QGraphicsSimpleTextItem),
        m_snapRange(100)
    {
        m_indicatorItem->setEnabled(false);
        m_indicatorItem->setVisible(false);
        QFont indicatorFont;
        indicatorFont.setPointSize(10);
        m_indicatorItem->setFont(indicatorFont);
        m_indicatorItem->setBrush(Qt::gray); // FIXME: palette
        m_indicatorItem->setFlag(QGraphicsItem::ItemIgnoresTransformations);

        m_defaultStrategy = new AutomaticSnapStrategy(this);
        m_strategies << m_defaultStrategy
                     << new NoSnapStrategy(this)
                     << new SnapToGridStrategy(this)
                     << new SnapToItemEndPointStrategy(this)
                     << new SnapToItemMidPointStrategy(this)
                     << new SnapToItemCenterStrategy(this)
                     << new SnapToItemShapeStrategy(this);

        m_actionGroup = new QActionGroup(this);
        for (auto strategy : m_strategies)
        {
            auto action = strategy->action();
            action->setActionGroup(m_actionGroup);
            m_actionToStrategy.insert(action, strategy);
        }

        connect(m_actionGroup, &QActionGroup::triggered,
                this, [this](QAction *action)
        {
            setActiveStrategy(m_actionToStrategy.value(action));
        });

        m_defaultStrategy->action()->setChecked(true);
        setActiveStrategy(m_defaultStrategy);
    }

    SnapManager::~SnapManager()
    {
    }

    QList<QAction *> SnapManager::actions() const
    {
        return m_actionGroup->actions();
    }

    int SnapManager::snapRange() const
    {
        return m_snapRange;
    }

    void SnapManager::setScene(Scene *scene)
    {
        if (m_scene != nullptr)
        {
            m_scene->removeEventFilter(this);
            m_scene->removeItem(m_indicatorItem);
        }

        m_scene = scene;

        if (m_scene != nullptr)
        {
            m_scene->addItem(m_indicatorItem);
            m_scene->installEventFilter(this);
        }

        setActiveStrategy(m_activeStrategy);
    }

    void SnapManager::setView(View *view)
    {
        m_view = view;
        setActiveStrategy(m_activeStrategy);
    }

    void SnapManager::applySettings(const LeGraphicsView::Settings &settings)
    {
        Q_UNUSED(settings);
    }

    void SnapManager::setSnapRange(int snapRange)
    {
        if (m_snapRange == snapRange)
            return;

        m_snapRange = snapRange;
        emit snapRangeChanged(snapRange);
    }

    void SnapManager::setActiveStrategy(AbstractSnapStrategy *strategy)
    {
        if (m_activeStrategy != nullptr)
        {
            m_activeStrategy->setScene(nullptr);
            m_activeStrategy->setView(nullptr);
        }

        m_activeStrategy = strategy;

        if (m_activeStrategy != nullptr)
        {
            m_activeStrategy->setScene(m_scene);
            m_activeStrategy->setView(m_view);
        }
    }

    // FIXME: setMaxSceneDistance(N*view->pixelSize());
    bool SnapManager::eventFilter(QObject *watched, QEvent *event)
    {
        Q_ASSERT(watched == m_scene);

        // Don't process events we don't understand
        switch(event->type())
        {
            case QEvent::GraphicsSceneMousePress:
            case QEvent::GraphicsSceneMouseRelease:
            case QEvent::GraphicsSceneMouseDoubleClick:
            case QEvent::GraphicsSceneMouseMove:
                break;
            default:
                return false;
        }

        auto gsmEvent = static_cast<QGraphicsSceneMouseEvent*>(event);
        auto mousePos = gsmEvent->scenePos();

        auto oldSnapPos = m_result.pos;
        removeHighlightEffect(m_result.item);
        m_indicatorItem->setVisible(false);

        qreal range = (m_view->mapToScene(m_snapRange, 0) - m_view->mapToScene(0, 0)).x();
        SnapResult result = m_activeStrategy->snap(mousePos, range);
        if (result.isValid())
        {
            m_result = result;
            gsmEvent->setScenePos(m_result.pos);
            addHighlightEffect(m_result.item);
            m_indicatorItem->setText(QString(" %1").arg(m_result.text));
            m_indicatorItem->setPos(m_result.pos);
            m_indicatorItem->setVisible(true);
            if (event->type() == QEvent::GraphicsSceneMouseMove)
            {
                // Filter if not changed
                return m_result.pos == oldSnapPos;
            }
            else
            {
                // Propagate
                return false;
            }
        }
        else
        {
            // Filter if no snap
            return true;
        }

    }

    void SnapManager::addHighlightEffect(GraphicsItem *item)
    {
        if (item == nullptr)
        {
            return;
        }

        auto effect = new QGraphicsDropShadowEffect(this);
        effect->setColor(Qt::gray); // FIXME: Need palette
        effect->setOffset(QPointF());
        effect->setBlurRadius(50);
        item->setGraphicsEffect(effect);
    }

    void SnapManager::removeHighlightEffect(GraphicsItem *item)
    {
        if (item == nullptr)
        {
            return;
        }

        item->setGraphicsEffect(nullptr);
    }
}
